
//#define DEFAULT_SERVER_PORT (1203)
#define DEFAULT_PORT (1204)
#define RESEND_DELAY (.5f)
#define INPUT_INTERVAL (0.1f)

// Libraries
#include <SFML/Graphics.hpp>
#include <SFML/Network.hpp>


// C++
#define _USE_MATH_DEFINES
#include <math.h>
#include <iostream>
#include <sstream>


// Local
#include "gameobject.h"
#include "network.h"
#include "input.h"
#include "sbconstants.h"
#include "packetparser.h"
#include "resourcemanager.h"
#include "change.h"
#include "renderablegameworld.h"
#include "logmanager.h"
using namespace std;

int diff = 0;


// Put into some functions collection
template <class T>
bool FromString(T& t, const std::string& s, std::ios_base& (*f)(std::ios_base&))
{
	std::istringstream iss(s);
	return !(iss >> f >> t).fail();
}

enum CLIENT_STATE {INIT, CONNECTING, SYNCHRONIZING, SYNCHRONIZED, PLAYING, QUITING};
std::string StateToString(CLIENT_STATE state)
{
	static std::string names[] = {"INIT", "CONNECTING", "SYNCHRONIZING", "SYNCHRONIZED", "PLAYING", "QUITING"};
	return names[state];
}

class Client : public PacketParser
{
public :

	std::string playerName;

	Client() : state(INIT), id(0), shipID(0), gameworld(), confirmedGameworld(true, NULL)
	{
		cout << "INIT" << endl;
		cameraZoom = 1.0f;
	};

	bool SetupConnection(unsigned short clientPort, sf::IPAddress serverIP, unsigned short serverPort)
	{
		if (!network.StartUDPListening(clientPort))
		{
			//std::cout << "Port " << clientPort << " not available" << std::endl;
			return false;
		}

		this->server = sf::IPAddress(serverIP);
		this->serverPort = serverPort;
		return true;
	}

	void DrawState(CLIENT_STATE state)
	{
		std::string text[] = {"INIT", "CONNECTING", "SYNCHRONIZING", "SYNCHRONIZED", "PLAYING", "QUITING"};
		if (((int)(timer.GetElapsedTime()*2.0f)) % 2)
		{
			ResourceManager::GetInstance().DrawText(*window, 50, 50, text[state] + std::string("..."));
		}
		else
		{
			ResourceManager::GetInstance().DrawText(*window, 50, 50, text[state]);
		}
	}

	void Run()
	{
		float lastPacketSent = 0;
		float lastPhysicsUpdate = timer.GetElapsedTime();
		window = new sf::RenderWindow(sf::VideoMode(800, 600, 32), "Space Bomber", sf::Style::Close);
		sf::RenderWindow& app = *window;
		app.ShowMouseCursor(false);
		std::string connecting = "Connecting to " + server.ToString();

		while (app.IsOpened())
		{
			// Process packets from server
			std::queue<PacketWithSource*>* packages = network.GetReadyPackages();
			if (packages != NULL)
			{
				while ( !packages->empty() )
				{
					PacketWithSource* packageWithSource = packages->front();
					packages->pop();
					HandlePacket(packageWithSource->sender, packageWithSource->port, packageWithSource->packet);
					delete packageWithSource;
				}
			}

			// Process non Game events
			sf::Event event;
			while (app.GetEvent(event))
			{
				if (event.Type == sf::Event::Closed
					|| (event.Type == sf::Event::KeyPressed && event.Key.Code == sf::Key::Escape))
				{
					sf::Packet data = CreatePacketHeader(id, MESSAGE::TYPE::CONNECT)
						<< MESSAGE::CONNECT::DISCONNECT;
					network.Send(data, server, serverPort);
					state = QUITING; // Ensure we don't send updated after loggin out.
					cout << "QUITTING" << endl;
				}

				// Q to quit the server
				else if (event.Type == sf::Event::KeyPressed && event.Key.Code == sf::Key::Q)
				{
					sf::Packet data = CreatePacketHeader(id, MESSAGE::TYPE::ADMIN)
						<< MESSAGE::ADMIN::SHUTDOWN;
						network.Send(data, server, serverPort);
				}
			}

			app.Clear(sf::Color(0, 0, 0));

			switch (state)
			{
				case INIT:
					// I'm not really using this state
					state = CONNECTING;
					cout << "CONNECTING" << endl;
				break;

				case CONNECTING:
					DrawState(state);

					// (Re)send connection
					if (lastPacketSent + RESEND_DELAY < timer.GetElapsedTime())
					{
						//string playerName = "Player";
						//std::cout << "Sending connection request to " << server.ToString() << std::endl;
						sf::Packet data = CreatePacketHeader(0, MESSAGE::TYPE::CONNECT)
							<< MESSAGE::CONNECT::CONNECT
							<< playerName;

						network.Send(data, server, serverPort);
						lastPacketSent = timer.GetElapsedTime();
					}

				break;

				case SYNCHRONIZING:
					DrawState(state);

					// After a connection, we have to recieve the entire world
					if (lastPacketSent + INPUT_INTERVAL < timer.GetElapsedTime())
					{
						lastPacketSent += INPUT_INTERVAL;
						sf::Packet data = CreatePacketHeader(id, MESSAGE::TYPE::CONNECT)
							<< MESSAGE::CONNECT::SYNCRONIZING
							<< confirmedChangeNumber;
						network.Send(data, server, serverPort);
					}
					break;

				case SYNCHRONIZED:
					DrawState(state);
					lastPhysicsUpdate = timer.GetElapsedTime();

					if (lastPacketSent + INPUT_INTERVAL < timer.GetElapsedTime())
					{
						lastPacketSent += INPUT_INTERVAL;
						sf::Packet data = CreatePacketHeader(id, MESSAGE::TYPE::CONNECT)
							<< MESSAGE::CONNECT::SYNCRONIZED;
						network.Send(data, server, serverPort);
					}
					break;

				case PLAYING:
				{
					// Update physics
					while (lastPhysicsUpdate + TIME_STEP < timer.GetElapsedTime())
					{
						lastPhysicsUpdate += TIME_STEP;
						gameworld.Step();
						ProcessCamera(); // Synched to the physics, and not framerate
					}

					// Transmit movement state
					if (lastPacketSent + INPUT_INTERVAL < timer.GetElapsedTime())
					{
						lastPacketSent += INPUT_INTERVAL;
						sf::Packet data = CreatePacketHeader(id, MESSAGE::TYPE::WORLD_UPDATE)
							<< confirmedChangeNumber
							<< SBInput::GetPlayerMoves(app.GetInput());
						network.Send(data, server, serverPort);
					}

					app.SetView(camera);
					gameworld.DrawObjects(app);

					static sf::String text("", ResourceManager::GetInstance().fontMedium, 20);
					text.SetPosition(10, 10);
					std::stringstream t;
					t << "Player ID: " << id << "   ";
					t << "Ship ID: " << shipID << "   ";
					t << "Gameworld to change: " << diff;
					text.SetText(t.str());
					app.SetView(app.GetDefaultView());
					app.Draw(text);
				}
				break;

				case QUITING:
					app.Close();
					break;

				default:
				break;
			}

			app.Display();
#ifdef WIN32
			Sleep(10);
#else
			usleep(10000);
#endif

		}
	}

private:
	sf::RenderWindow* window;
	sf::Clock timer;
	CLIENT_STATE state;
	sf::Uint32 id; // Client id on server
	sf::Uint32 shipID; // Game object to control
	RenderableGameWorld gameworld;
	GameWorld confirmedGameworld;
	sf::Uint32 confirmedChangeNumber;

	// TODO: Move these to renderable world
	sf::View camera;
	float cameraZoom;

	Network network;
	sf::IPAddress server;
	unsigned short clientPort;
	unsigned short serverPort;

	void ProcessCamera()
	{
		GameObject* myShip = gameworld.GetObject(shipID);
		if (myShip != NULL)
		{
			sf::Vector2f curPos = camera.GetCenter();
			curPos.x += (myShip->pos.x - curPos.x) / 50.f;
			curPos.y += (myShip->pos.y - curPos.y) / 50.f;
			camera.SetCenter(curPos);

			float length = sqrtf(myShip->vel.x * myShip->vel.x + myShip->vel.y * myShip->vel.y);
			float targetZoom = (1 - length / 300.f);
			cameraZoom += (targetZoom - cameraZoom) / 500.f;
			camera.SetHalfSize(sf::Vector2f(400, 300));

			camera.Zoom(max(0.50f, cameraZoom));
		}
	}

	void ProcessWorldUpdate(Change& change)
	{
		while (change.timestamp > confirmedGameworld.worldTime)
		{
			confirmedGameworld.Step();
		}

		switch (change.type)
		{
			case MESSAGE::WORLD_UPDATE::CONFIRMED:
				break;

			case MESSAGE::WORLD_UPDATE::CREATE_OBJ:
				confirmedGameworld.InsertObject(*change.newObject);

				{
					stringstream ss;
					ss << "Created object: " << change.newObject->id;
					LogManager::Log("client", ss.str());
				}
				break;

			case MESSAGE::WORLD_UPDATE::DESTROY_OBJ:
				confirmedGameworld.DeleteObject(change.destoyedObjectID);
				break;

			case MESSAGE::WORLD_UPDATE::SHIP_CONTROL:
				{
					GameObject* obj = confirmedGameworld.GetObject(change.shipControl.shipID);
					if (obj != NULL)
					{
						confirmedGameworld.UpdateShipControls(*obj, change.shipControl.controls);
					}
					else
					{
						stringstream ss;
						ss << "WARN recieved WORLD_UPDATE for a non existing ship: " << change.shipControl.shipID;
						LogManager::Log("client", ss.str());
					}
				}
				break;

			default:
				{
					stringstream ss;
					ss << "WARN recieved unknow WORLD_UPDATE type: " << change.type;
					LogManager::Log("client", ss.str());
				}
				break;
		}
	}

	void HandleAdminPacket(
		sf::IPAddress sender,
		unsigned short port,
		sf::Uint32 clientID,
		sf::Packet& packet)
	{
		stringstream ss;
		ss << "Rx Admin packet From " << clientID;
		LogManager::Log("client", ss.str());
	}

	void HandleConnectionPacket(
		sf::IPAddress sender,
		unsigned short port,
		sf::Uint32 clientID,
		sf::Packet& packet)
	{
		sf::Uint8 type;
		packet >> type;

		//stringstream ss;
		//ss << "Rx Connection packet from " << clientID << " of type " << (int)type << " while in " << StateToString(state);
		//LogManager::Log("packetparser", ss.str());

		switch (type)
		{
			case MESSAGE::CONNECT::CONNECT:
				if (state == CONNECTING)
				{
					id = clientID;
					packet >> confirmedGameworld.worldTime;
					state = SYNCHRONIZING;
					cout << "SYNCHRONIZING" << endl;
					confirmedChangeNumber = 0;
					//std::cout << "Received client and ship ID from server (" << id <<", " << shipID << ")" << std::endl;
				}
				// Else remove packet
				else
				{
					packet.Clear();
				}
				break;

			case MESSAGE::CONNECT::SYNCRONIZING:
				if (state == SYNCHRONIZING)
				{
					Change change;
					while (not packet.EndOfPacket())
					{
						packet >> change;
						if (change.number <= confirmedChangeNumber)
							continue;

						ProcessWorldUpdate(change);
						confirmedChangeNumber = change.number;
					}
				}
				else
				{
					packet.Clear();
				}
				break;

			case MESSAGE::CONNECT::SYNCRONIZED:
				if (state == SYNCHRONIZING)
				{
					packet >> shipID;
					packet >> confirmedChangeNumber;
					state = SYNCHRONIZED;
					cout << "SYNCHRONIZED" << endl;
				}
				else
				{
					packet.Clear();
				}
				break;

			case MESSAGE::CONNECT::DISCONNECT:
				// Hmm someone disconnect from the client??
				break;

			default:
				//std::cout << "Unhandled connection packet: " << type << std::endl;
				break;
		}
	}

	void HandleWorldUpdatePacket(
		sf::IPAddress sender,
		unsigned short port,
		sf::Uint32 clientID,
		sf::Packet& packet)
	{
		if (state == SYNCHRONIZED)
		{
			state = PLAYING;
		}

		if (state == PLAYING)
		{
			bool isWorldInSync = true;
			Change change;
			while (!packet.EndOfPacket())
			{
				packet >> change;
				diff = change.timestamp - gameworld.worldTime;
				if (change.number <= confirmedChangeNumber)
					continue;

				ProcessWorldUpdate(change);
				confirmedChangeNumber = change.number;
				isWorldInSync = false;
			}

			// Step back to last
			if (!isWorldInSync)
			{
				gameworld.SynchronizeFromWorld(confirmedGameworld);
			}
		}
		else
		{
			std::stringstream ss;
			ss << "Packet WorldUpdate received while in state " << StateToString(state) << " packet body: " << PacketToString(packet);
			LogManager::Log("packetparser", ss.str());
			packet.Clear();
		}
	}
};



int main(int numArguments, char** arguments)
{
	LogManager::Initialize("client_log");

	unsigned short serverPort = SERVER_DEFAULT_PORT;
	unsigned short port = DEFAULT_PORT;
	sf::IPAddress server("127.0.0.1");

	// Override by passed parameters
	if (numArguments > 1)
	{
		server = sf::IPAddress(arguments[1]);
	}

	if (!server.IsValid())
	{
		//std::cout << "Address " << server.ToString() << " is not valid" << std::endl;
		return EXIT_FAILURE;
	}

	if (numArguments > 2)
	{
		// Convert port to short
		if (!FromString<unsigned short>(port, arguments[2], std::dec))
		{
			//std::cout << "Cannot convert " << arguments[2] << " to a port number" << std::endl;
			return EXIT_FAILURE;
		}
	}

	std::string playerName = "Unknown alien"; //sf::IPAddress::GetPublicAddress().ToString();
	if (numArguments > 3)
	{
		// Player name
		playerName = std::string(arguments[3]);
	}

	Client client;
	client.playerName = playerName;
	if ( client.SetupConnection(port, server, serverPort) )
	{
		client.Run();
	}
	else
	{
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}
